#!/usr/bin/python
from Tkinter import *
import socket, sys, time, os, re

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = 'localhost'
PORT = 1060
TIMER_PERIOD_MS = 500
BLINK_FREQ_BPS = 1 # should be not greater than 500/TIMER_PERIOD_MS
BOTHER_PERIOD_S = 8

def recv_all(sock, length):
    data = ''
    while len(data) < length:
        more = sock.recv(length - len(data))
        if not more:
            raise EOFError('Received %d bytes are shorter than expected %d-byte command' %\
                (len(data), length))
        data += more
    return data

def frame(root, side):
    w = Frame(root)
    w.pack(side=side, expand=YES, fill=BOTH)
    return w

def button(root, side, text, command=None):
    w = Button(root, text=text, command=command)
    w.pack(side=side, expand=YES, fill=BOTH)
    return w

class StatTimer(Frame):
    def __init__(self, master):
        Frame.__init__(self, master)
        self.pack(expand=YES, fill=BOTH)
        self.master.title('Statistics Timer')

        # timer window
        self.time = StringVar()
        self.timeF = frame(self, TOP)
        self.ss_but = button(self.timeF, LEFT, '-', lambda w=self,\
            s='sta_next': w.process_cmd(s))
        self.nl_but = button(self.timeF, LEFT, 'n', lambda w=self,\
            s='stop_rst': w.process_cmd(s))

        self.time_ent = Entry(self.timeF, relief=SUNKEN, textvariable=self.time,
            width=12, validate='focus',
            validatecommand=self.validate_time_ent)
        self.time_ent.pack(side=RIGHT, expand=YES, fill=BOTH)

        # statistics window
        self.stat = StringVar()
        statF = frame(self, TOP)

        self.stat_scr = Scrollbar(statF)
        self.stat_txt = Text(statF,font=('times',12), width=12, height=10)

        self.stat_txt.focus_set()
        self.stat_scr.pack(side=RIGHT, fill=Y)
        self.stat_txt.pack(side=LEFT, fill=Y)
        self.stat_scr.config(command=self.stat_txt.yview)
        self.stat_txt.config(yscrollcommand=self.stat_scr.set)

        # internal timer state
        self.running = False
        self.since_lap   = 0.0
        self.total   = 0.0
        self.total_lap   = 0.0
        self.lap_count = 0
        self.threshold   = 600; # in seconds

        # socket init
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.setblocking(0)
        s.bind((HOST, PORT))
        s.listen(1)

        # schedule periodic update
        self.after(TIMER_PERIOD_MS, self.advance_time)

    def set_time_ent(self, val):
        if (self.timeF.focus_get() != self.time_ent or self.running):
            self.time.set(val)

    def validate_time_ent(self):
        match = re.search(r'[ ]*[\-\d]+[ ]*:[ ]*[\-\d]+[ ]*<[ ]*(\d+)[ ]*:[ ]*(\d+)[ ]*', self.time.get())
        if match:
            self.threshold = int(match.group(1))*60 + int(match.group(2))
        return True

    def to_time(self, t4c):
        return '%02d:%02d' % (t4c/60, t4c%60)

    def to_time_w_hours(self, t4c):
        return '%02d:%02d:%02d' % (t4c/60/60, t4c/60%60, t4c%60)

    def process_cmd(self, cmd):
        self.validate_time_ent()
        if (cmd == 'sta_next'):
            if (self.running):
                # next
                ctime = time.time()
                self.total_lap += ctime - self.since_lap
                self.total     += ctime - self.since_lap
                self.since_lap = ctime
                self.stat_report('Lap%d %s' % (self.lap_count, self.to_time_w_hours(int(self.total_lap))))
                self.total_lap = 0.0
                self.lap_count += 1
            else:
                # start
                self.running = True
                ctime = time.time()
                self.since_lap = ctime
        elif (cmd == 'stop_rst'):
            if (self.running):
                # stop
                self.running = False
                ctime = time.time()
                self.total_lap += ctime - self.since_lap
                self.total     += ctime - self.since_lap
                self.since_lap = ctime
                self.stat_report('-Lap%d %s' % (self.lap_count, self.to_time_w_hours(int(self.total_lap))))
                self.total_lap = 0.0
                self.lap_count += 1
            else:
                # reset
                self.stat_report('Race %s\n' % self.to_time_w_hours(int(self.total)))
                self.total = 0.0
                self.total_lap = 0.0
                self.lap_count = 0

        else:
            self.stat_report('Error: Unknown command got via socket: %s' % cmd)

    def stat_report(self, rep_str):
       self.stat_txt.insert(END, "%s\n" % rep_str)
       self.stat_txt.yview(MOVETO, 1.0)

    def advance_time(self):
        try:
            sc, sockname = s.accept()
            message = recv_all(sc, 8)
            self.process_cmd(message)
            sc.close()
        except EOFError as ex:
            self.stat_report('Error: %s' % ex)
        except:
            pass

        # update time window
        if (self.running):
            # calculate running time
            run_time = int(time.time() - self.since_lap)
            # update visual controls
            self.set_time_ent('%s < %s' % (self.to_time(run_time),
                self.to_time(self.threshold)) )
            self.ss_but['text'] = '*'
            # blinking logic
            fast_time = int((time.time() - self.since_lap)*2*
                            BLINK_FREQ_BPS)
            if (run_time > self.threshold and fast_time%2 == 0):
                self.time_ent['bg'] = "red"
            elif (self.time_ent['bg'] != "white"):
                self.time_ent['bg'] = "white"
            # deiconify logic
            if ( run_time > self.threshold and
                 ( self.threshold - run_time) % BOTHER_PERIOD_S == 0 and
                 not self.master.winfo_viewable() ):
                self.master.deiconify()

        else:
            # update visual controls
            if (self.total == 0.0):
                self.set_time_ent('--:-- < %s' % self.to_time(self.threshold))
            else:
                self.set_time_ent('%s < %s' % (self.to_time(int(self.total_lap)),
                    self.to_time(self.threshold)) )
            self.ss_but['text'] = '-'
            if (self.time_ent['bg'] != "white"):
                self.time_ent['bg'] = "white"
            # deiconify logic
            if ( not self.master.winfo_viewable() ):
                self.master.deiconify()

        # schedule periodic update
        self.after(TIMER_PERIOD_MS, self.advance_time)

if __name__ == '__main__':
    CMD = sys.argv.pop() if len(sys.argv) == 3 else None
    if sys.argv[1:] == ['client'] and CMD != None:
        s.connect((HOST, PORT))
        s.sendall(CMD)
        s.close()
    else:
        root = Tk()
        root.wm_attributes('-topmost', 1) 
        StatTimer(root).mainloop()

